package com.brillilab.service.logic.instrument;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.brillilab.common.constant.ResultEnum;
import com.brillilab.common.entity.PageVo;
import com.brillilab.common.entity.ResponseVo;
import com.brillilab.common.exceptions.BrillilabException;
import com.brillilab.service.quartz.BrillQuartzScheduler;
import com.brillilab.service.utils.QuartzUtils;
import com.brillilab.common.utils.TimeUtil;
import com.brillilab.domain.enums.common.BoolEnum;
import com.brillilab.domain.enums.instrument.AppointmentStateEnum;
import com.brillilab.domain.enums.instrument.CancelTypeEnum;
import com.brillilab.domain.enums.instrument.InstrumentStateEnum;
import com.brillilab.domain.po.instrument.Instrument;
import com.brillilab.domain.po.instrument.InstrumentAppointment;
import com.brillilab.domain.po.instrument.InstrumentMaintenance;
import com.brillilab.domain.vo.instrument.AppointmentVo;
import com.brillilab.domain.vo.user.UserInfoVo;
import com.brillilab.service.constant.JobConstants;
import com.brillilab.service.core.instrument.*;
import com.brillilab.service.job.instrument.AppointmentAlert;
import com.brillilab.service.job.instrument.AppointmentEnd;
import com.brillilab.service.job.instrument.AppointmentStart;
import com.brillilab.service.logic.instrument.converter.AppointmentConverter;
import com.brillilab.service.logic.user.UsersLogic;
import com.brillilab.service.module.message.producer.MessageProducer;
import com.brillilab.service.module.message.template.MessageTemplate;
import com.brillilab.service.module.message.template.impl.instrument.AppointmentCancelTemplate;
import com.brillilab.service.module.message.template.impl.instrument.AppointmentChangeTemplate;
import com.brillilab.service.module.message.template.impl.instrument.AppointmentMakeTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
@Slf4j
public class AppointmentLogic {
    @Resource
    private InstrumentLogic instrumentLogic;
    @Resource
    private IInstrumentService instrumentService;
    @Resource
    private IInstrumentMaintenanceService maintenanceService;
    @Resource
    private IInstrumentAppointmentService appointmentService;
    @Resource
    private UsersLogic usersLogic;
    @Resource
    private MessageProducer messageProducer;
    @Autowired
    private BrillQuartzScheduler quartzScheduler;


    /**
     * 添加预约/修改预约
     *
     * @param appointmentVo
     * @param userInfo
     * @return
     */
    public InstrumentAppointment createOrUpdateNewAppointment(AppointmentVo appointmentVo,UserInfoVo userInfo) {

        boolean update=appointmentVo.getId()!=null;

        //对象转换
        InstrumentAppointment appointment=AppointmentConverter.appointmentVoToAppointment(appointmentVo);
        //非可用组成员无法预约
        boolean canUse=instrumentLogic.canUse(appointment.getInstrumId(),userInfo);
        if(!canUse){
            throw new BrillilabException(ResultEnum.USER_NOT_POWER);
        }

        // （1）防止修改他人预约
        InstrumentAppointment exist=null;
        if(appointment.getId() != null){
            exist=appointmentService.selectById(appointment.getId());
            Assert.isTrue(exist.getUserId().equals(userInfo.getId()),"无法修改他人预约！");
        } else {
            appointment.setUserId(userInfo.getId());
        }
        // （2）获取仪器，判断仪器是否可用
        Instrument instrument=instrumentService.selectById(appointment.getInstrumId());
        Assert.isTrue(instrument!=null && instrument.getIsDelete().equals(BoolEnum.FALSE.getValue()),"仪器不存在！");
        Assert.isTrue(!instrument.getState().equals(InstrumentStateEnum.STOP_USE.getValue()),"仪器已停用，无法进行预约！");

        // （3）添加必要字段值
        appointment.setUserName(userInfo.getName());

        // （4）获取未开和正在进行预约，判断是否重合
        Assert.isTrue(!checkAppointmentExist(appointment),"与已有预约时间冲突，无法进行预约！");
        // （5）获取维护，判断预约与维护时间是否重合
        Assert.isTrue(!checkMaintaneceExist(appointment),"与已有维护时间冲突，无法进行预约！");

        Assert.isTrue(appointment.getStartTime().getTime() - System.currentTimeMillis() > 600000,"请提前10分钟进行预约！");

        // （6）新增/修改预约
        if(exist != null){
            appointmentService.updateById(appointment);
        } else {
            appointmentService.insert(appointment);
        }

        appointment=appointmentService.selectById(appointment.getId());
        Assert.notNull(appointment,"业务操作失败！");
        UserInfoVo apUser=usersLogic.getUserInfo(userInfo.getLabId(),appointment.getUserId());
        Assert.notNull(apUser,"仪器预约用户不存在于该实验室！");

        // （7）设置定时任务，定时更改预约状态
        createAppointmentJob(appointment,apUser);

        // 再次确认
        // （4）获取未开和正在进行预约，判断是否重合
        Assert.isTrue(!checkAppointmentExist(appointment),"与已有预约时间冲突，无法进行预约！");
        // （5）获取维护，判断预约与维护时间是否重合
        Assert.isTrue(!checkMaintaneceExist(appointment),"与已有维护时间冲突，无法进行预约！");

        //TODO （8）通知用户预约成功
        sendMakeAppointmentMessage(appointment,instrument,apUser,update);

        return appointment;

    }

    private void sendMakeAppointmentMessage(InstrumentAppointment appointment,Instrument instrument,UserInfoVo userInfo,boolean update) {
        MessageTemplate messageTemplate = null;
        if(update){
            messageTemplate=new AppointmentChangeTemplate(instrument,appointment,userInfo);
        }else {
            messageTemplate=new AppointmentMakeTemplate(instrument,appointment,userInfo);
        }
        messageProducer.sendMessagePush(messageTemplate);
    }

    //核对预约是否存在
    private boolean checkAppointmentExist(InstrumentAppointment appointment) {
        List<InstrumentAppointment> appointments=appointmentService.selectList(appointment.getInstrumId(),AppointmentStateEnum.NOT_START.getValue(),AppointmentStateEnum.START.getValue());
        if(!CollectionUtils.isEmpty(appointments)){
            for (InstrumentAppointment e : appointments) {
                if(TimeUtil.isCoincidence(appointment.getStartTime(),e.getStartTime(),appointment.getEndTime(),
                        e.getEndTime()) && !e.getId().equals(appointment.getId()))
                    return true;
            }
        }
        return false;
    }

    //检查预约是否与维护冲突
    private boolean checkMaintaneceExist(InstrumentAppointment appointment) {
        List<InstrumentMaintenance> maintenances=maintenanceService.selectList(appointment.getInstrumId());
        if(!CollectionUtils.isEmpty(maintenances)){
            Calendar calendar=Calendar.getInstance();
            calendar.setTime(appointment.getStartTime());
            int year=calendar.get(Calendar.YEAR);
            int month=calendar.get(Calendar.MONTH) + 1;
            int date=calendar.get(Calendar.DATE);
            int dayOfWeek=calendar.get(Calendar.DAY_OF_WEEK) - 1;

            boolean isConsis = false;
            for (InstrumentMaintenance maintenance : maintenances) {
                isConsis = maintenanceService.compareMaintenanceWhitAppointment(appointment,year,month,date,dayOfWeek,maintenance);
                if(isConsis){
                    return true;
                }
            }
        }
        return false;
    }

    //创建预约定时任务
    private void createAppointmentJob(InstrumentAppointment appointment,UserInfoVo userInfo) {

        HashMap<String, Object> dataMap=new HashMap<>();
        String appStr=JSONObject.toJSON(appointment).toString();
        dataMap.put("appointment",appStr);
        dataMap.put("labId",userInfo.getLabId());
        dataMap.put("receiverId",userInfo.getId());
        dataMap.put("labName",userInfo.getLabName());
        dataMap.put("labMemberId",userInfo.getLabMemberId());

        String startCron=QuartzUtils.getCronExpr(appointment.getStartTime());
        String startJobId=JobConstants.JOB_GROUP_APPOINTMENT_START + appointment.getId().toString();
        String endCron=QuartzUtils.getCronExpr(appointment.getEndTime());
        String endJobId=JobConstants.JOB_GROUP_APPOINTMENT_END + appointment.getId().toString();

        Date alert=new Date(appointment.getStartTime().getTime() - 10 * 60 * 1000);
        String alertCron=QuartzUtils.getCronExpr(alert);
        String alertJobId=JobConstants.JOB_GROUP_APPOINTMENT_AlERT + appointment.getId().toString();


        if(appointment.getId() != null){
            quartzScheduler.deleteJob(startJobId,JobConstants.JOB_GROUP_APPOINTMENT_START);
            quartzScheduler.deleteJob(endJobId,JobConstants.JOB_GROUP_APPOINTMENT_END);
            quartzScheduler.deleteJob(alertJobId,JobConstants.JOB_GROUP_APPOINTMENT_AlERT);
        }
        quartzScheduler.createJob(AppointmentStart.class,startJobId,JobConstants.JOB_GROUP_APPOINTMENT_START,
                startCron,dataMap);
        quartzScheduler.createJob(AppointmentEnd.class,endJobId,JobConstants.JOB_GROUP_APPOINTMENT_END,endCron,
                dataMap);
        quartzScheduler.createJob(AppointmentAlert.class,alertJobId,JobConstants.JOB_GROUP_APPOINTMENT_AlERT,alertCron,
                dataMap);
    }

    /**
     * 取消预约
     *
     * @param id
     * @param userInfo
     */
    public ResponseVo cancelAppointment(Long id,UserInfoVo userInfo) {


        //（1）判断操作者是预约者还是责任人
        InstrumentAppointment appointment=appointmentService.selectById(id);
        Assert.notNull(appointment,"预约不存在！");
        Assert.isTrue(!appointment.getState().equals(AppointmentStateEnum.CANCEL.getValue()),"预约已被取消，无法重复取消！");
        
        Instrument instrument=instrumentService.selectById(appointment.getInstrumId());
        Assert.notNull(instrument,"仪器不存在！");
        
        //（2）非预约者和责任人的操作抛出异常
        boolean isManager=instrumentLogic.isManagerAndDuty(instrument.getDutyId(),userInfo);
        if(!userInfo.getId().equals(appointment.getUserId()) && !isManager){
            throw new BrillilabException(ResultEnum.USER_NOT_POWER);
        }

        UserInfoVo apUser=usersLogic.getUserInfo(userInfo.getLabId(),appointment.getUserId());
        Assert.notNull(apUser,"仪器预约用户不存在于该实验室！");

        //（3）取消预约并通知
        cancelThenSendMessage(appointment,instrument,apUser,isManager,userInfo.getId());

        //（4）删除预约定时任务
        deleteAppointmentJob(appointment);

        return ResponseVo.success();

    }

    //删除预约并发送消息
    private void cancelThenSendMessage(InstrumentAppointment appointment,Instrument instrument,UserInfoVo userInfo,boolean isManager,Long operatorId) {

        if(operatorId.equals(appointment.getUserId())){
            //本人
            appointment.setCancelReason(CancelTypeEnum.USER_CANCEL.getValue());
            appointment.setState(AppointmentStateEnum.CANCEL.getValue());
            appointmentService.updateById(appointment);
            // TODO 发消息
            sendCancelAppointmentMessage(CancelTypeEnum.USER_CANCEL.getValue(),instrument,appointment,userInfo);
            log.info("预约被用户取消，预约ID：" + appointment.getId());
            return;
        }else if(isManager){
            //可仪器管理的人
            appointment.setCancelReason(CancelTypeEnum.ADMIN_CANCEL.getValue());
            appointmentService.updateById(appointment);
            appointmentService.updateById(appointment);
            sendCancelAppointmentMessage(CancelTypeEnum.ADMIN_CANCEL.getValue(),instrument,appointment,userInfo);
            log.info("预约被管理员取消，预约ID：" + appointment.getId());
        }
    }

    @Async
    public void sendCancelAppointmentMessage(Integer cancelType,Instrument instrument,InstrumentAppointment appointment,UserInfoVo userInfo) {
        AppointmentCancelTemplate appointmentCancelTemplate=new AppointmentCancelTemplate(cancelType,instrument,appointment,userInfo);
        messageProducer.sendMessagePush(appointmentCancelTemplate);
    }

    //删除预约的定时任务
    private void deleteAppointmentJob(InstrumentAppointment appointment){
        String endJobId=JobConstants.JOB_GROUP_APPOINTMENT_END + appointment.getId().toString();
        String startJobId=JobConstants.JOB_GROUP_APPOINTMENT_START + appointment.getId().toString();
        quartzScheduler.deleteJob(startJobId,JobConstants.JOB_GROUP_APPOINTMENT_START);
        quartzScheduler.deleteJob(endJobId,JobConstants.JOB_GROUP_APPOINTMENT_END);
    }

    /**
     * 预约列表
     *
     * @return
     */
    public PageVo<AppointmentVo> getAppointmentList(Long instrumId,UserInfoVo userInfo,Integer appointType,Integer pageNum,Integer pageSize) {


        //封装查询参数
        LambdaQueryWrapper<InstrumentAppointment> entityWrapper=new LambdaQueryWrapper<>();

        if(instrumId != null){
            entityWrapper.eq(InstrumentAppointment::getInstrumId,instrumId);
        } else {
            //该实验室未删除的仪器的预约
            List<Instrument> instruments=instrumentService.selectList(userInfo.getLabId(),BoolEnum.FALSE.getValue());
            List<Long> ids=instruments.stream().map(Instrument::getId).collect(Collectors.toList());
            if(CollectionUtils.isEmpty(ids)){
                entityWrapper.eq(InstrumentAppointment::getInstrumId,0);
            } else {
                entityWrapper.in(InstrumentAppointment::getInstrumId,ids);
            }
            if(userInfo.getId() != null){
                entityWrapper.eq(InstrumentAppointment::getUserId,userInfo.getId());
            }
        }

        if(appointType.equals(AppointmentStateEnum.START.getValue())){
            entityWrapper.in(InstrumentAppointment::getState,Arrays.asList(AppointmentStateEnum.NOT_START.getValue(),AppointmentStateEnum.START.getValue()));
        } else if(appointType.equals(AppointmentStateEnum.END.getValue()) || appointType.equals(AppointmentStateEnum.CANCEL.getValue())){
            entityWrapper.eq(InstrumentAppointment::getState,appointType );
        }

        //获取总记录条数
        int total=appointmentService.selectCount(entityWrapper);

        // 按状态查询预约列表
        IPage<InstrumentAppointment> appointmentPage=appointmentService.selectPage(pageNum,pageSize,entityWrapper);

        List<InstrumentAppointment> appointments=appointmentPage.getRecords();
        List<Long> instrumentIds=appointments.stream().map(InstrumentAppointment::getInstrumId).distinct().collect(Collectors.toList());
        List<Instrument> instruments = instrumentService.selectList(instrumentIds);

        List<InstrumentAppointment> list=appointmentPage.getRecords().stream().map(appointment -> {
            instruments.stream()
                    .filter(e -> e.getId().equals(appointment.getInstrumId()))
                    .findFirst()
                    .ifPresent(instrument -> appointment.setInstrumName(instrument.getName()));
            return appointment;
        }).collect(Collectors.toList());

        //转换
        List<AppointmentVo> appointmentVos=AppointmentConverter.appointmentListToappointmentVoList(appointmentPage.getRecords());

        //分页
        PageVo<AppointmentVo> appointmentPageVo=new PageVo<>(pageNum,pageSize,total,appointmentVos);

        return appointmentPageVo;
    }

    /**
     * 在日程插件中显示预约
     * @param instrumId
     * @param userInfo
     * @return
     */
    public List<AppointmentVo> getAllAppointmentOnScheduler(Long instrumId,UserInfoVo userInfo) {

        List<InstrumentAppointment> appointments=appointmentService.selectList(instrumId,AppointmentStateEnum.NOT_START.getValue(),AppointmentStateEnum.START.getValue(),AppointmentStateEnum.END.getValue());

        List<AppointmentVo> appointmentVos=AppointmentConverter.appointmentListToappointmentVoList(appointments);

        return appointmentVos;
    }
}
